package cn.liumouren.util;

import cn.hutool.core.collection.CollUtil;
import cn.hutool.core.collection.CollectionUtil;
import org.apache.poi.ss.formula.functions.T;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.data.redis.core.RedisTemplate;
import org.springframework.data.redis.core.script.DefaultRedisScript;
import org.springframework.stereotype.Component;

import java.time.Duration;
import java.util.Collection;
import java.util.List;
import java.util.concurrent.TimeUnit;

/**
 * @author Daniel Liu
 * @date 2021/1/31 22:28
 */
@Component
public class RedisUtil {

    private static RedisTemplate<Object, Object> redisTemplate;

    @Autowired
    public void setRedisTemplate(RedisTemplate<Object, Object> redisTemplate) {
        RedisUtil.redisTemplate = redisTemplate;
    }

    public static void set(String key, Object value, Duration ttl) {
        redisTemplate.opsForValue().set(key, value, ttl);
    }

    public static void set(String key, Object value) {
        redisTemplate.opsForValue().set(key, value);
    }


    @SuppressWarnings("unchecked")
    public static <T> T get(String key) {
        return (T) redisTemplate.opsForValue().get(key);
    }


    @SuppressWarnings("unchecked")
    public static <T> T get(String key, Class<T> clazz) {
        return (T) redisTemplate.opsForValue().get(key);
    }

    /**
     * 更新key值
     * 会保留本来的过期时间
     *
     * @param key
     * @param newValue
     */
    public static void updateValue(String key, Object newValue) {
        Boolean hasKey = redisTemplate.hasKey(key);
        if (hasKey != null && hasKey) {
            Long expire = redisTemplate.getExpire(key);
            expire = expire == null ? -1 : expire;
            if (expire == -1) {
                set(key, newValue);
            } else {
                set(key, newValue, Duration.ofSeconds(expire));
            }
        }
    }

    @SuppressWarnings("unchecked")
    public static <T> List<T> multiGet(Collection<Object> keys) {
        return (List<T>) redisTemplate.opsForValue().multiGet(keys);
    }


    public static void remove(String key) {
        redisTemplate.delete(key);
    }

    public static void remove(Collection<String> keys) {
        redisTemplate.delete(keys);
    }


    /**
     * 获取并删除该值
     *
     * @param key
     * @param <T>
     * @return
     */
    public static <T> T getAndRemove(String key) {
        T t = get(key);
        remove(key);
        return t;
    }

    public static void leftPush(String key, Object value) {
        redisTemplate.opsForList().leftPush(key, value);
    }

    public static void rightPush(String key, Object value) {
        redisTemplate.opsForList().rightPush(key, value);
    }

    public static void rightPushAll(String key, Collection<Object> values) {
        redisTemplate.opsForList().rightPushAll(key, values);
    }

    public static void rightPushAll(String key, Collection<Object> values, long ttl, TimeUnit timeUnit) {
        if (hasKey(key)) {
            rightPushAll(key, values);
        } else {
            rightPushAll(key, values);
            redisTemplate.expire(key, ttl, timeUnit);
        }
    }

    public static long sizeOfList(String key) {
        Long size = redisTemplate.opsForList().size(key);
        return size == null ? 0 : size;
    }


    /**
     * 如果key存在直接push,
     * 如果key不存在, 则给key设置过期时间
     *
     * @param key
     * @param value
     * @param ttl
     * @param timeUnit
     */
    public static void leftPush(String key, Object value, long ttl, TimeUnit timeUnit) {
        if (hasKey(key)) {
            leftPush(key, value);
        } else {
            // 没有key, 设置过期时间
            leftPush(key, value);
            redisTemplate.expire(key, ttl, timeUnit);
        }
    }

    private static boolean hasKey(String key) {
        Boolean hasKey = redisTemplate.hasKey(key);
        return hasKey != null && hasKey;
    }

    public static void rightPush(String key, Object value, long ttl, TimeUnit timeUnit) {
        if (hasKey(key)) {
            rightPush(key, value);
        } else {
            // 没有key, 设置过期时间
            rightPush(key, value);
            redisTemplate.expire(key, ttl, timeUnit);
        }
    }

    @SuppressWarnings("unchecked")
    public static <T> List<T> range(String key, long start, long end) {
        return (List<T>) redisTemplate.opsForList().range(key, start, end);
    }


    /**
     * 分布式锁setnx
     * 如果key不存在 返回true
     * 存在 返回false
     *
     * @param key
     * @param value
     * @return
     */
    private static Boolean setIfAbsent(String key, Object value, Duration d) {
        return redisTemplate.opsForValue().setIfAbsent(key, value, d);
    }

    /**
     * 分布式锁
     *
     * @param key      key
     * @param value    持锁者的标识
     * @param duration 锁住的时间
     * @return
     */
    public static Boolean tryLock(String key, String value, Duration duration) {
        return setIfAbsent(key, value, duration);
    }

    /**
     * 解锁
     *
     * @param key   key
     * @param value 持锁者的标识, 与lock时的值对比, 判断是不是同一个人上的锁
     * @return
     */
    public static Boolean unlock(String key, String value) {
        // 使用Lua脚本：先判断是否是自己设置的锁，再执行删除
        // 使用lua脚本删除redis中匹配value的key，可以避免由于方法执行时间过长而redis锁自动过期失效的时候误删其他线程的锁
        // TODO spring自带的执行脚本方法中，集群模式直接抛出不支持执行脚本的异常EvalSha is not supported in cluster environment.，所以只能拿到原redis的connection来执行脚本
        String script = "if redis.call('get', KEYS[1]) == ARGV[1] then return redis.call('del', KEYS[1]) else return 0 end";
        DefaultRedisScript<List> rs = new DefaultRedisScript<>(script, List.class);
        List list = redisTemplate.execute(rs, CollUtil.newArrayList(key), value);
        if (CollUtil.isEmpty(list)) {
            return false;
        }
        return Long.parseLong(list.get(0).toString()) == 1L;
    }

}
